#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# assumed bugs:
#  * rekursiv gelöschte verzeichnisse und verschobene verzeichnisstrukturen wahrscheinlich noch nicht korrekt behandelt
#  	* creates in neuen verzeichnissen dürfen im log nicht vor dem create des verzeichnisses stehen
#  	* deletes innerhalb eines verzeichnisses müssen vor dem delete des verzeichnisses gehandelt werden

import sys, os, datetime
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent

old_version = False
try:
    from pyinotify import IN_DELETE, IN_CREATE, IN_MODIFY, IN_MOVED_FROM, IN_MOVED_TO, IN_ISDIR
except ImportError:
    from pyinotify import ThreadedNotifier
    IN_DELETE = EventsCodes.IN_DELETE
    IN_CREATE = EventsCodes.IN_CREATE
    IN_MODIFY = EventsCodes.IN_MODIFY
    IN_MOVED_FROM = EventsCodes.IN_MOVED_FROM
    IN_MOVED_TO = EventsCodes.IN_MOVED_TO
    IN_ISDIR = EventsCodes.IN_ISDIR
    old_version = True

# watched events
mask = IN_DELETE | IN_CREATE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO 
watch_dir = "/tmp"
if len(sys.argv) > 1:
    watch_dir = os.path.normpath(sys.argv[1])

class ProcEvt(ProcessEvent):
    def __init__(self, wm):
	self.evt_log = {}
	self.created = []
	self.watches = {}
	self.watch_manager = wm

	# define event handlers
	self.process_IN_CREATE = 	self.record_event
	self.process_IN_DELETE = 	self.record_event
	self.process_IN_MODIFY = 	self.record_event
	self.process_IN_MOVED_FROM = 	self.record_event
	self.process_IN_MOVED_TO = 	self.record_event

	# remove obsolete watches
	self.process_IN_IGNORED = 	lambda e:self.rm_watch(e.path)

    def record_event(self, event):
	path = os.path.join(event.path, event.name)
	self.evt_log[path] = event
	print event.event_name, path, "\t", event
    
	if event.mask == IN_CREATE|IN_ISDIR:
	    self.add_watch(path) 

	# shrinking the log...
	if event.mask in [IN_CREATE, IN_CREATE|IN_ISDIR]:
	    self.created.append(path)

	elif event.mask in [IN_DELETE, IN_MOVED_FROM, IN_DELETE|IN_ISDIR]:
	    if path in self.created:
		self.evt_log.pop(path)
		self.created.remove(path)
		print "last entry eliminated", path

    def add_watch(self, path):
	watch = self.watch_manager.add_watch(path, mask, rec=True)
	self.watches.update( watch )	# add_watch returns a dict

    def rm_watch(self, path):
	if path in self.watches.keys():
	    self.watch_manager.rm_watch( self.watches[path] )
	    self.watches.pop(path)


wm = WatchManager()
wm.add_watch(watch_dir, mask, rec=True)

proc_evt = ProcEvt(wm)

if not old_version:
    notifier = Notifier(wm, proc_evt)
else:
    notifier = ThreadedNotifier(wm, proc_evt)


start = datetime.datetime.now()
print "start monitoring", repr(watch_dir), "at", start

if not old_version:
    notifier.loop()
else:
    notifier.start()

    while True:
        try:
            import time
            time.sleep(1)
        except KeyboardInterrupt:
            # ...until c^c signal
            print 'stop monitoring...'
            # stop monitoring
            notifier.stop()
            break
        except Exception, err:
            # otherwise keep on looping
            print err

stop = datetime.datetime.now()

def event2cmd(event):
	path = os.path.join(event.path, event.name)
	if event.event_name in "IN_CREATE IN_MODIFY IN_MOVED_TO".split():
	    return "cp $(BASE)/%s $(BACKUP)/%s"%(path, path)

	elif event.event_name == "IN_DELETE IN_MOVED_FROM".split():
	    return "rm -f $(BASE)/%s"%path



# open log file
log_path = os.path.expanduser("~/journal-%s.log"%watch_dir.replace(os.sep,"-"))
log = open(log_path, "a")

print
print "JOURNAL for dir", repr(watch_dir), "from", start, "until", stop
print >> log, "# JOURNAL for dir", repr(watch_dir), "from", start, "until", stop
print >> log, "export BASE=%s"%watch_dir

for path, evt in proc_evt.evt_log.items():
    
    print evt.event_name, path, "\t", evt
    #print "cmd:", event2cmd(evt)

    # write journal
    print >> log, "#", evt.event_name, path, "\t", evt
    print >> log, event2cmd(evt)
